<template>
  <div class="vue-container">
    <el-dialog
      ref="maxDialog"
      v-drag
      :title="ruleForm.id ? '编辑用户' : '新增用户'"
      :visible.sync="dialogVisible"
      width="800px"
      @closed="closedDialog"
    >
      <el-form
        ref="reform"
        v-loading="loading"
        :model="ruleForm"
        status-icon
        :rules="rules"
        :size="size"
        label-width="90px"
        :show-message="showMessage"
        element-loading-background="rgba(255, 255, 255, 0.8)"
        element-loading-text="数据正在加载中"
        element-loading-spinner="el-icon-loading"
      >
        <!--自己写flex布局 不要直接用element的layout布局 有bug会导致布局错乱-->
        <div class="flex-warp">
          <div class="width-50">
            <el-form-item label="用户账号" prop="username">
              <el-input
                v-model.trim="ruleForm.username"
                placeholder="请输入用户账号"
                :disabled="!!ruleForm.id"
                type="text"
                show-word-limit
                autocomplete="off"
                :maxlength="15"
              />
            </el-form-item>
          </div>
          <div v-if="!ruleForm.id" class="width-50">
            <el-form-item label="用户密码" prop="password">
              <el-input
                v-model="ruleForm.password"
                placeholder="请输入用户密码"
                show-password
                autocomplete="off"
                :maxlength="20"
              />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户名称" prop="name">
              <el-input
                v-model.trim="ruleForm.name"
                placeholder="请输入用户名称"
                type="text"
                :maxlength="10"
                autocomplete="off"
              />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="手机号码" prop="mobile">
              <el-input
                v-model.trim="ruleForm.mobile"
                placeholder="请输入手机号码"
                type="text"
                autocomplete="off"
                :maxlength="12"
                oninput="value=value.replace(/\D/g, '')"
                @blur="ruleForm.mobile = $event.target.value"
              />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户邮箱" prop="email">
              <el-input
                v-model.trim="ruleForm.email"
                placeholder="请输入用户邮箱"
                type="text"
                autocomplete="off"
                :maxlength="25"
              />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="排序" prop="sort">
              <el-input-number
                v-model.trim="ruleForm.sort"
                :min="0"
                placeholder="请输入排序"
                style="width: 100%;"
                show-word-limit
                autocomplete="off"
              />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户性别" prop="sex">
              <el-radio-group v-model="ruleForm.sex">
                <el-radio
                  v-for="(item, index) in obj.sexList"
                  :key="index"
                  :label="Number(item.code)"
                >
                  {{ item.label }}
                </el-radio>
              </el-radio-group>
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="账号状态" prop="status">
              <el-radio-group v-model="ruleForm.status" :disabled="ruleForm.isAdmin">
                <el-radio
                  v-for="(item, index) in obj.statusList"
                  :key="index"
                  :label="Number(item.code)"
                >{{ item.label }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户部门" prop="department">
              <treeselect
                v-if="!ruleForm.departmentName"
                ref="treeselect"
                :key="refreshKey"
                v-model="ruleForm.departmentId"
                :options="options"
                :async="isAsync"
                :normalizer="normalizer"
                :load-options="isAsync ? asyncOptions : loadOptions"
                :multiple="false"
                :show-count="false"
                :class="size"
                placeholder="请选择用户部门"
                @keydown.native="treeKeydown"
                @open="itemopen"
                @select="handleSelect"
              />
              <el-input v-else v-model="ruleForm.departmentName" :disabled="true" />
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户角色" prop="roles">
              <el-select
                v-model="ruleForm.roleIds"
                :disabled="ruleForm.isAdmin"
                style="width: 100%;"
                filterable
                multiple
                collapse-tags
                placeholder="请选择用户角色"
              >
                <el-option
                  v-for="(item, index) in roles"
                  :key="index"
                  :label="item.name"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </div>
          <div class="width-50">
            <el-form-item label="用户岗位" prop="postIds">
              <el-select
                v-model="ruleForm.postIds"
                style="width: 100%;"
                filterable
                multiple
                collapse-tags
                placeholder="请选择用户岗位"
              >
                <el-option
                  v-for="(item, index) in posts"
                  :key="index"
                  :label="item.name"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </div>
          <div class="width-100">
            <el-form-item label="备注说明" prop="remarks">
              <el-input
                v-model.trim="ruleForm.remarks"
                placeholder="请输入备注"
                type="textarea"
                show-word-limit
                autocomplete="off"
                style="width: 100%;"
              />
            </el-form-item>
          </div>
        </div>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button :size="size" @click="dialogVisible = false">取消</el-button>
        <el-button v-throttle :size="size" :loading="loading" type="primary" @click="submitForm">确定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
import { mixin } from '@/views/pages/mixin/save'
import md5 from 'js-md5'
import * as API_role from '@/api/system/role'
import * as API_post from '@/api/system/post'
import { list as tree, parentIds } from '@/api/system/department'
import { validPassword, validUsername } from '@/utils/validate'
export default {
  name: 'UserSave',
  mixins: [mixin],
  data() {
    const validateUsername = (rule, value, callback) => {
      if (!value) {
        callback(new Error('请输入用户账号'))
      } else if (value.length < 5 || value.length > 15) {
        callback(new Error('账号长度在5-15位之间'))
      } else if (!validUsername(value)) {
        callback(new Error('账号格式不正确，仅支持字母、数字和下划线'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (!value) {
        callback(new Error('请输入用户密码'))
      } else if (value.length < 6 || value.length > 20) {
        callback(new Error('密码长度在6-20位之间'))
      } else if (!validPassword(value)) {
        callback(new Error('密码格式不正确，仅支持字母、数字和下划线'))
      } else {
        callback()
      }
    }
    return {
      // 表单对象
      ruleForm: {
        avatar: '',
        status: 1,
        sex: 0,
        sort: 0
      },
      // 是否异步搜索
      isAsync: false,
      // 部门树参数结构转换
      normalizer(node) {
        return {
          label: node.name,
          parentId: node.parentId,
          isLeaf: node.isLeaf
        }
      },
      // 更新树的key
      refreshKey: 0,
      // 计算展开接口的响应时间
      duration: 50,
      // 部门树
      options: [],
      // 部门树初始备份数据
      deptList: [],
      // 表单校验
      rules: {
        username: [
          { required: true, validator: validateUsername, trigger: 'blur' }
        ],
        password: [
          { required: true, validator: validatePassword, trigger: 'blur' }
        ],
        name: [
          { required: true, message: '请输入用户昵称', trigger: 'blur' }
        ],
        mobile: [
          { required: true, message: '请输入手机号码', trigger: 'blur' },
          { pattern: /^1[3-9]\d{9}$/, message: '手机号格式错误' }
        ],
        sort: [
          { required: true, message: '请输入排序', trigger: 'blur' }
        ]

      },
      roles: [], // 角色列表
      posts: [], // 岗位列表
      // 计时器
      inval: null,
      // 父节点数组
      parentIds: [],
      // 加载子节点后储存对象
      loadMap: {}
    }
  },
  watch: {
    'ruleForm.parentId': {
      handler(val) {
        // 若值被清空且在非打开弹窗初始化情况下
        if (!val && Object.keys(this.loadMap).length) {
          this.createTree()
        }
      },
      deep: true
    }
  },
  beforeDestroy() {
    if (this.inval) clearInterval(this.inval)
  },
  methods: {
    // 弹出窗口加载
    async openDialog(rows) {
      this.getRoleAll()
      this.getPostAll()
      // 初始化调用部门树
      const res = await tree({ parentId: '0' })
      if (res.data && res.data.length) {
        this.options = res.data.map(item => {
          if (!item.isLeaf) item.children = null
          return item
        })
        // 部门初始备份
        this.deptList = [...this.options]
        this.refreshKey += 1
      }
      this.dialogVisible = true
      if (rows.id) {
        this.getDetail(rows.id)
        this.ruleForm = { ...this.ruleForm, ...rows }
        // 若departmentId存在，则计时默认展开至此部门
        if (rows.departmentId) {
          this.loadToCurrent(rows.departmentId)
        }
      }
    },
    // 获取用户详情
    getDetail(id) {
      this.funs['detail'](id).then(res => {
        this.ruleForm = res.data
        if (this.ruleForm.isAdmin) {
          this.roles.push({
            id: this.ruleForm.roleIds[0],
            code: 'admin',
            name: '超级管理员'
          })
        }
      })
    },
    // 获取角色列表
    getRoleAll() {
      API_role.all().then(res => {
        this.roles = res.data
      })
    },
    // 获取岗位列表
    getPostAll() {
      API_post.all().then(res => {
        this.posts = res.data
      })
    },
    // 树按键监听
    treeKeydown() {
      this.isAsync = true
    },
    // 树下拉打开监听
    itemopen() {
      if (this.isAsync) this.createTree()
    },
    // 初始赋值
    createTree() {
      // 若打开/收起时未选择异步搜索结果，关闭异步搜索并重置树数据
      this.isAsync = false
      // 树重新赋值
      this.options = [...this.obj.arr]
      this.$nextTick(() => {
        if (this.ruleForm.parentId) this.loadToCurrent(this.ruleForm.parentId)
      })
    },
    // 懒加载找到当前选中节点
    loadToCurrent(id) {
      this.loading = true
      if (this.parentIds && this.parentIds.length) {
        this.nodeTree(this.parentIds)
      } else {
        // 树懒加载数据回显
        parentIds(id).then(async res => {
          this.parentIds = res.data || []
          this.nodeTree(res.data || [])
        }).catch(() => {
          this.loading = false
        })
      }
    },
    // 计时加载每一个父节点
    nodeTree(data) {
      if (data.length) {
        let i = 0
        let count = 0
        // 针对需要回显的节点进行默认展开，计时时间为每次请求接口的响应时间
        this.inval = setInterval(() => {
          count++
          // 若计时次数超过100停止计时，返回数据错误提示
          if (count > 100) {
            clearInterval(this.inval)
            this.$message.error('数据错误，计时超时')
            return
          }
          const node = this.$refs.treeselect.getNode(data[i])
          // 若取不到nestedSearchLabel说明节点未渲染完毕
          if (node.nestedSearchLabel) {
            node.isExpanded = true
            // 调用loadChildrenOptions进行节点展开后的数据渲染
            this.$refs.treeselect.loadChildrenOptions({ ...node })
            i += 1
            if (i >= data.length) {
              // 关闭计时器
              clearInterval(this.inval)
              this.loading = false
            }
          }
        }, this.duration)
      } else {
        this.loading = false
      }
    },
    // 树懒加载
    async loadOptions({ arr, parentNode }) {
      if (arr) {
        this.options = this.getData(arr)
      } else {
        const startTime = new Date().getTime()
        if (!(parentNode.id in this.loadMap)) {
          const res = await tree({ parentId: parentNode.id })
          this.duration = (new Date().getTime() + 50) - startTime
          this.$set(parentNode, 'children', this.getData(res.data))
          this.loadMap[parentNode.id] = this.getData(res.data)
        } else {
          this.duration = 50
        }
        this.$set(parentNode, 'children', this.loadMap[parentNode.id])
      }
    },
    // 远程搜索
    asyncOptions({ searchQuery, callback }) {
      tree({ keyword: searchQuery }).then(res => {
        const options = this.getData(res.data, false)
        callback(null, options)
      })
    },
    // 懒加载及异步搜索数据处理
    getData(data, isLeaf = true) {
      return data.length ? data.map(option => {
        const obj = {
          id: option.id,
          name: option.name,
          parentId: option.parentId,
          isLeaf: option.isLeaf
        }
        if (!option.isLeaf && isLeaf) {
          obj.children = null
        }
        return obj
      }) : []
    },
    // 上级部门选中触发
    handleSelect(e) {
      if (this.isAsync) {
        this.isAsync = false
        this.loadOptions({ arr: [e] })
      }
    },
    // 保存前操作
    submitStart() {
      this.ruleForm.isStatusChange = false
      if (this.ruleForm.password && this.ruleForm.password.length > 0) {
        this.ruleForm.password = md5(this.ruleForm.password)
      }
    },
    // 弹窗关闭动画结束触发
    closedDialog() {
      this.ruleForm = {
        status: 1,
        sex: 0,
        sort: 0,
        avatar: ''
      }
      this.$refs.reform.resetFields()
      this.parentIds = []
      this.loadMap = {}
      if (this.inval) clearInterval(this.inval)
    }
  }
}
</script>
<style lang="scss" scoped>
.flex-warp {
  display: flex;
  flex-wrap: wrap;
}
.width-50 {
  width: 50%;
}
.width-100 {
  width: 100%;
}
</style>
